package com.google.code.swingonfire.border;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;

import javax.swing.border.AbstractBorder;

import com.google.code.swingonfire.utility.Colors;

public class ShadowBorder extends AbstractBorder
{

    private static final long serialVersionUID = 7272052312860934291L;

    public static ShadowBorder create()
    {
        return create(8);
    }

    public static ShadowBorder create(int size)
    {
        return create(size, new Color(0xcf1e1700, true));
    }

    public static ShadowBorder create(int size, Color shadowColor)
    {
        return create(size, shadowColor, new Color(0x0f000000, true));
    }

    public static ShadowBorder create(int size, Color shadowColor, Color lineColor)
    {
        int offsetX = size / 8;
        int offsetY = size / 2;
        int insets = size + Math.max(Math.abs(offsetX), Math.abs(offsetY));

        return new ShadowBorder(offsetX, offsetY, size, shadowColor, Colors.translucent(shadowColor, 0), lineColor,
            new Insets(insets, insets, insets, insets));
    }

    private final int offsetX;
    private final int offsetY;
    private final int size;
    private final Color innerColor;
    private final Color outerColor;
    private final Color lineColor;
    private final Insets insets;

    public ShadowBorder(int offsetX, int offsetY, int size, Color innerColor, Color outerColor, Color lineColor,
        Insets insets)
    {
        super();

        this.offsetX = offsetX;
        this.offsetY = offsetY;
        this.size = size;
        this.innerColor = innerColor;
        this.outerColor = outerColor;
        this.lineColor = lineColor;
        this.insets = insets;
    }

    @Override
    public Insets getBorderInsets(Component c)
    {
        return insets;
    }

    @Override
    public Insets getBorderInsets(Component c, Insets insets)
    {
        return super.getBorderInsets(c, insets);
    }

    @Override
    public void paintBorder(Component c, Graphics graphics, int x, int y, int width, int height)
    {
        ShadowBorderCacheKey key = new ShadowBorderCacheKey(size, innerColor, outerColor);
        ShadowBorderCacheEntry entry = ShadowBorderCache.getInstance().get(key);

        int x2 = x + width;
        int y2 = y + height;

        int dx = ((x + insets.left) - size) + offsetX;
        int dy = ((y + insets.top) - size) + offsetY;
        int dx2 = (x2 - insets.right) + size + offsetX;
        int dy2 = (y2 - insets.bottom) + size + offsetY;
        int ds = key.getSize() * 2;

        if (insets.top > 0)
        {
            Graphics2D g = (Graphics2D) graphics.create();
            g.clipRect(x, y, width, insets.top);
            paintBorder(g, entry, x, y, x2, y2, dx, dy, dx2, dy2, ds);
        }

        if (insets.bottom > 0)
        {
            Graphics2D g = (Graphics2D) graphics.create();
            g.clipRect(x, y2 - insets.bottom, width, insets.bottom);
            paintBorder(g, entry, x, y, x2, y2, dx, dy, dx2, dy2, ds);
        }

        if (insets.left > 0)
        {
            Graphics2D g = (Graphics2D) graphics.create();
            g.clipRect(x, y + insets.top, insets.left, y2 - insets.bottom - y - insets.top);
            paintBorder(g, entry, x, y, x2, y2, dx, dy, dx2, dy2, ds);
        }

        if (insets.right > 0)
        {
            Graphics2D g = (Graphics2D) graphics.create();
            g.clipRect(x2 - insets.right, y + insets.top, insets.right, y2 - insets.bottom - y - insets.top);
            paintBorder(g, entry, x, y, x2, y2, dx, dy, dx2, dy2, ds);
        }
    }

    private void paintBorder(Graphics2D g, ShadowBorderCacheEntry entry, int x, int y, int x2, int y2, int dx, int dy,
        int dx2, int dy2, int ds)
    {
        g.drawImage(entry.getTopLeftImage(), dx, dy, null);
        g.drawImage(entry.getTopImage(), dx + ds, dy, dx2 - ds, dy + ds, 0, 0, 1, ds, null);
        g.drawImage(entry.getTopRightImage(), dx2 - ds, dy, null);
        g.drawImage(entry.getLeftImage(), dx, dy + ds, dx + ds, dy2 - ds, 0, 0, ds, 1, null);
        g.drawImage(entry.getRightImage(), dx2 - ds, dy + ds, dx2, dy2 - ds, 0, 0, ds, 1, null);
        g.drawImage(entry.getBottomLeftImage(), dx, dy2 - ds, null);
        g.drawImage(entry.getBottomImage(), dx + ds, dy2 - ds, dx2 - ds, dy2, 0, 0, 1, ds, null);
        g.drawImage(entry.getBottomRightImage(), dx2 - ds, dy2 - ds, null);

        g.setColor(innerColor);
        g.fillRect(dx + ds, dy + ds, Math.max(0, dx2 - dx - (2 * ds)), Math.max(0, dy2 - dy - (2 * ds)));

        g.setColor(outerColor);

        if (dx > x)
        {
            g.fillRect(x, dy, dx - x, dy2 - dy);
        }

        if (dy > y)
        {
            g.fillRect(x, y, x2 - x, dy - y);
        }

        if (dx2 < x2)
        {
            g.fillRect(dx2, dy, x2 - dx2, dy2 - dy);
        }

        if (dy2 < y2)
        {
            g.fillRect(x, dy2, x2 - x, y2 - dy2);
        }

        if (lineColor != null)
        {
            g.setColor(lineColor);
            g.drawRect((x + insets.left) - 1, (y + insets.top) - 1, (x2 - insets.left - insets.right - x) + 1, (y2
                - insets.top - insets.bottom - x) + 1);
        }
    }
}
